package com.bac.rentmap.manager.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Query;

import org.springframework.beans.factory.annotation.Required;

import com.bac.rentmap.ad.flat.LocalAddress;
import com.bac.rentmap.distance.DistanceCalculator;
import com.bac.rentmap.distance.DistanceCheckPoint;
import com.bac.rentmap.jdo.JdoSupport;
import com.bac.rentmap.manager.BuildingManager;
import com.bac.rentmap.model.Building;
import com.google.appengine.api.datastore.GeoPt;

/**
 *
 * @author Ivan Holub
 */
public class BuildingManagerImpl extends JdoSupport implements BuildingManager {
	private static final Logger log = Logger
			.getLogger(BuildingManagerImpl.class.getName());
	private DistanceCalculator distanceCalculator;

	public List<Building> getBuildings(int number) {
		PersistenceManager pm = getPersistenceManager();
		Query query = pm.newQuery(Building.class);
		query.setRange(0, number);
		List<Building> list = (List<Building>) query.execute();
		list.size();
		pm.close();
		return list;
	}

	public void saveBuildings(List<Building> buildings, String city) {
		// DatastoreService datastoreService =
		// DatastoreServiceFactory.getDatastoreService();
		// List<Entity> it = new ArrayList<Entity>();
		// for (Building b : buildings) {
		// Entity e = new Entity("Building");
		// e.setProperty("lat", b.getLat());
		// e.setProperty("lon", b.getLon());
		// e.setProperty("housenumber", b.getHousenumber());
		// e.setProperty("street", b.getStreet());
		// it.add(e);
		// }
		// List<Key> list = datastoreService.put(it);
		PersistenceManager pm = getPersistenceManager();
		int maxNumber = getMaxNumberImpl(pm, city);
		int i = maxNumber;
		for (Building b : buildings) {
			i++;
			b.setNumber(i);
		}
		pm.makePersistentAll(buildings);
		pm.close();
	}

	public Building findBuilding(String city, LocalAddress localAddress) {
		PersistenceManager persistenceManager = getPersistenceManager();
		try {
			Query query = persistenceManager.newQuery(Building.class);
			// TODO: handle building part and street prefix
			query
					.setFilter("city == cityParam && lowercaseStreet == streetParam && housenumber == housenumberParam");
			query
					.declareParameters("String cityParam, String streetParam, Integer housenumberParam");
			List<Building> buildings = (List<Building>) query.execute(city,
					localAddress.getStreet().toLowerCase(), localAddress.getBuilding());
			if (buildings.isEmpty())
				return null;
			Building res = buildings.get(0);
			if (buildings.size() > 1)
				log.warning("More than one building found for " + localAddress
						+ ", returning first one: " + res);
			return res;
		} finally {
			persistenceManager.close();
		}
	}

	public void clearBuildings(int number) {
		PersistenceManager pm = getPersistenceManager();
		Query query = pm.newQuery(Building.class);
		query.setOrdering("number desc");
		query.setRange(0, number);
		List<Building> list = (List<Building>) query.execute();
		list.size();
		for (Building b : list) {
			pm.deletePersistent(b);
		}
		pm.close();
	}

	public int getBuildingsCount() {
		PersistenceManager pm = getPersistenceManager();
		String queryStr = "select count() from " + Building.class.getName();
		Query query = pm.newQuery(queryStr);
		int retValue = (Integer) query.execute();
		pm.close();
		return retValue;
	}

	public int getMaxNumber(String city) {
		PersistenceManager persistenceManager = getPersistenceManager();
		try {
			return getMaxNumberImpl(persistenceManager, city);
		} finally {
			persistenceManager.close();
		}
	}

	private int getMaxNumberImpl(PersistenceManager persistenceManager, String city) {
		Query query = persistenceManager.newQuery(Building.class);
		query.setFilter("city == cityParam");
        query.declareParameters("String cityParam");
		query.setOrdering("number desc");
		query.setRange(0, 1);
		List<Building> list = (List<Building>) query.execute(city);
		int retValue = 0;
		if (list.size() > 0) {
			Building b = list.get(0);
			if (b.getNumber() == null) {
				retValue = -1;
			} else {
				retValue = Integer.valueOf(b.getNumber());
			}
		}
		return retValue;
	}

	public Building findNearestBuilding(String city, GeoPt geoPt) {
		log.info("Searching nearest buildings to " + geoPt);
		PersistenceManager persistenceManager = getPersistenceManager();
		try {
			List<Building> buildings = new ArrayList<Building>(200);
			buildings.addAll(findBuildingsWithGreaterLat(persistenceManager,
					city, geoPt));
			buildings.addAll(findBuildingsWithLessLat(persistenceManager, city,
					geoPt));
			log.finest("Found buildings: " + buildings);
			return findNearestBuilding(buildings, geoPt);
		} finally {
			persistenceManager.close();
		}
	}

	private List<Building> findBuildingsWithLessLat(
			PersistenceManager persistenceManager, String city, GeoPt geoPt) {
		Query query = persistenceManager.newQuery(Building.class);
		query.setFilter("city == cityParam && geoPt <= geoPtParam");
		query
				.declareParameters("String cityParam, com.google.appengine.api.datastore.GeoPt geoPtParam");
		query.setOrdering("geoPt desc");
		query.setRange(0, 100);
		return (List<Building>) query.execute(city, geoPt);
	}

	private List<Building> findBuildingsWithGreaterLat(
			PersistenceManager persistenceManager, String city, GeoPt geoPt) {
		Query query = persistenceManager.newQuery(Building.class);
		query.setFilter("city == cityParam && geoPt > geoPtParam");
		query
				.declareParameters("String cityParam, com.google.appengine.api.datastore.GeoPt geoPtParam");
		query.setOrdering("geoPt asc");
		query.setRange(0, 100);
		return (List<Building>) query.execute(city, geoPt);
	}

	private Building findNearestBuilding(List<Building> buildings, GeoPt geoPt) {
		if (buildings.isEmpty())
			return null;
		List<DistanceCheckPoint> distanceCheckPoints = new ArrayList<DistanceCheckPoint>(
				buildings.size());
		for (Building building : buildings) {
			distanceCheckPoints.add(new DistanceCheckPoint(building.getGeoPt()
					.getLatitude(), building.getGeoPt().getLongitude()));
		}
		int index = distanceCalculator.findNearestDistanceCheckPointIndex(geoPt
				.getLatitude(), geoPt.getLongitude(), distanceCheckPoints);
		return buildings.get(index);

	}

	@Required
	public void setDistanceCalculator(DistanceCalculator distanceCalculator) {
		this.distanceCalculator = distanceCalculator;
	}
}
